In [1]:
    
import math
    
In [2]:
    
def is_odd(n):
    "Check if n is odd"
    # Check if n is divisible by 2
    if n%2 == 0:
        return False
    else:
        return True
# Test the function for integers from 0 to 10
for i in range(11):
    print('Is', i, 'odd?', is_odd(i))
    
    
Write a single function that takes the components of a vector of length 2 or 3 and returns the magnitude. Use default arguments to handle vectors of length 2 or 3 with the same code. Test your function for correctness against hand calculations for a selection of values.
In [3]:
    
def magnitude(x, y, z=0):
    "Calculate the length of a 2 or 3-dimensional vector"
    return math.sqrt(x**2 + y**2 + z**2)
# Test some cases
print(magnitude(0, 0)) # 0
print(magnitude(1, 1)) # square root of 2
print(magnitude(3, 4)) # 5
print(magnitude(3, 4, 5)) # 7.07106...
    
    
Given the coordinates of the vertices of a triangle, $(x_0, y_0)$, $(x_1, y_1)$ and $(x_2, y_2)$, the area $A$ of a triangle is given by:
$$ A = \left| \frac{x_0(y_1 - y_2) + x_1(y_2 - y_0) + x_2(y_0 - y_1)}{2} \right| $$Write a function that computes the area of a triangle given the coordinates of the vertices. Test the output of your function against some known solutions.
In [4]:
    
def area(x0, y0, x1, y1, x2, y2):
    "Calculate the area of a triangle given the vertices coordinates"
    return abs((x0*(y1-y2) + x1*(y2-y0) + x2*(y0-y1)) / 2)
# Test some cases
print(area(0, 0, 1, 0, 0, 1)) # 0.5
print(area(0, 0, 5, 0, 0, 8)) # 20
    
    
The factorial of a non-negative integer $n$ ($n!$) is expressed recursively by:
$$ n! = \begin{cases} 1 & n = 0 \\ (n - 1)! \,n & n > 0 \end{cases} $$Using recursion, program a function for computing the factorial.
Test your function against the math.factorial function, e.g.
In [5]:
    
def fact(n):
    "Calculate factorial recursively"
    if n == 0:
        return 1
    else:
        return n*fact(n-1)
# Test the function against the factorial from math module
print('Reference factorial:', math.factorial(5))
print('Calculated factorial:',fact(5))
    
    
Restructure your program from the bisection exercise in Activity 02 to
and then
For the first step, use a function for evaluating $f$, e.g.:
def f(x):
    # Put body of the function f(x) here, and return the function value
For the second step, encapsulate the bisection algorithm in a function:
def compute_root(f, x0, x1, tol, max_it):
    # Implement bisection algorithm here, and return when tolerance is satisfied or
    # number of iterations exceeds max_it
    # Return the approximate root, value of f(x) and the number of iterations
    return x, f, num_it
# Compute approximate root of the function f
x, f_x, num_it = compute_root(f, x0=3, x1=6, tol=1.0e-6, max_it=1000)
Try testing your program for a different function. A quadratic function, whose roots you can analytically, would be a good test case.
Function for evaluating $f$.
Note that this method does not work for functions tanget to the $x$ axis, such as $x^2 - 8x + 16 = (x - 4)^2$.
In [8]:
    
def f(x):
    #return x**3 - 6*x**2 + 4*x + 12
    return x**2 + x - 20 # Roots = -5, 4
    
Bisection algorithm function:
In [11]:
    
def compute_root(f, x0, x1, tol, max_it):
    """Computes the root of f between x0 and x1 using bisection,
        stops if the value of f at the root is under tol or if max_it is reached
        and returns the root, the value of f at the root and the number of iterations"""
    for i in range(max_it):
    
        # Compute x_mid
        x_mid = (x0 + x1) / 2
        # Compute f for the three values
        f_0, f_1, f_mid = f(x0), f(x1), f(x_mid)
        # Check the value of f_0*f_mid to determine how to update the endpoints
        if f_0*f_mid < 0:
            x1 = x_mid
        else:
            x0 = x_mid
        
        # Check if f is under tol
        if abs(f_mid) < tol:
            return x_mid, f_mid, i+1
    # Return the approximate root in case max_it is reached
    return x_mid, f_mid, i+1
# Test for the function f
x, f_x, num_it = compute_root(f, x0=3, x1=6, tol=1.0e-6, max_it=1000)
print('Approximate root:', x)
print('Value of f:', f_x)
print('Number of iterations:', num_it)
print('-----------------------------------------------------------')
x, f_x, num_it = compute_root(f, x0=-7, x1=0, tol=1.0e-6, max_it=1000)
print('Approximate root:', x)
print('Value of f:', f_x)
print('Number of iterations:', num_it)
    
    
In [12]:
    
def compute_root_rec(f, x0, x1, tol, max_it):
    """Computes the root of f between x0 and x1 using bisection recursively,
        stops if the value of f at the root is under tol or if max_it is reached
        and returns the root, the value of f at the root and the number of iterations"""
    # Base case 1: value under minimum tollerance
    if abs(f((x1+x0)/2)) < tol:
        return (x1+x0)/2, f((x1+x0)/2), max_it
    
    # Base case 2: maximum number of iterations reached
    elif max_it == 1:
        return (x1+x0)/2, f((x1+x0)/2), max_it
    
    # Check the value of f_0*f_mid to determine how to update the endpoints
    elif f(x0)*f((x1+x0)/2) < 0:
        return compute_root_rec(f, x0, (x1+x0)/2, tol, max_it-1)
    else:
        return compute_root_rec(f, (x1+x0)/2, x1, tol, max_it-1)
# Test for the function f
x, f_x, num_it = compute_root(f, x0=3, x1=6, tol=1.0e-6, max_it=1000)
print('Approximate root:', x)
print('Value of f:', f_x)
print('Number of iterations:', num_it)
print('-----------------------------------------------------------')
x, f_x, num_it = compute_root(f, x0=-7, x1=0, tol=1.0e-6, max_it=1000)
print('Approximate root:', x)
print('Value of f:', f_x)
print('Number of iterations:', num_it)